﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using NMF.Tests;
using System.Collections.Generic;

namespace NMF.Optimizations.Tests.UnitTests
{
    internal class Dummy {}

    internal class TestObserver<T> : ISourceObserver<T>
    {
        public Action<T> OnNewItem { get; set; }
        public Action<T> OnUpdatedItem { get; set; }
        public Action<T> OnDeletedItem { get; set; }
        public Action OnReset { get; set; }

        public bool NewItemCalled { get; set; }
        public bool UpdatedItemCalled { get; set; }
        public bool DeletedItemCalled { get; set; }
        public bool ResetCalled { get; set; }

        void ISourceObserver<T>.OnNewItem(T item)
        {
            if (OnNewItem != null) OnNewItem(item);
            NewItemCalled = true;
        }

        void ISourceObserver<T>.OnUpdatedItem(T item)
        {
            if (OnUpdatedItem != null) OnUpdatedItem(item);
            UpdatedItemCalled = true;
        }

        void ISourceObserver<T>.OnDeletedItem(T item)
        {
            if (OnDeletedItem != null) OnDeletedItem(item);
            DeletedItemCalled = true;
        }

        void ISourceObserver<T>.OnReset()
        {
            if (OnReset != null) OnReset();
            ResetCalled = true;
        }

        public void Reset()
        {
            NewItemCalled = false;
            UpdatedItemCalled = false;
            DeletedItemCalled = false;
            ResetCalled = false;

            OnNewItem = null;
            OnUpdatedItem = null;
            OnDeletedItem = null;
            OnReset = null;
        }
    }

    [TestClass]
    public class ObservableSourceTest
    {
        private ObservableSource<Dummy> items = new ObservableSource<Dummy>();
        private Dummy dummy1 = new Dummy();
        private Dummy dummy2 = new Dummy();
        private Dummy dummy3 = new Dummy();

        private TestObserver<Dummy> testobserver = new TestObserver<Dummy>();

        [TestInitialize]
        public void Initialize()
        {
            items.Subscribe(testobserver);
        }

        [TestMethod]
        public void Optimizations_ObservableSource_Contains()
        {
            items.Publish(dummy1);
            items.Publish(dummy2);

            Assert.IsTrue(items.Contains(dummy1));
            Assert.IsTrue(items.Contains(dummy2));
            Assert.IsFalse(items.Contains(dummy3));
        }

        [TestMethod]
        public void Optimizations_ObservableSource_GetEnumerator()
        {
            Assert.AreEqual(0, items.Count);
            items.Publish(dummy1);
            Assert.AreEqual(1, items.Count);
            items.Publish(dummy2);
            Assert.AreEqual(2, items.Count);
            items.Publish(dummy3);
            Assert.AreEqual(3, items.Count);

            items.AssertContainsOnly(dummy1, dummy2, dummy3);
        }

        [TestMethod]
        public void Optimizations_ObservableSource_Constructor()
        {
            var list = new List<Dummy>() { dummy1, dummy2, dummy3 };

            items = new ObservableSource<Dummy>(list);

            items.AssertContainsOnly(dummy1, dummy2, dummy3);
        }

        [TestMethod]
        public void Optimizations_ObservableSource_Publish()
        {
            testobserver.OnNewItem = item => Assert.AreEqual(dummy1, item);

            items.Publish(dummy1);

            testobserver.NewItemCalled = false;
            testobserver.OnNewItem = item => Assert.AreEqual(dummy2, item);

            items.Publish(dummy2);

            Assert.IsTrue(testobserver.NewItemCalled);
            Assert.IsFalse(testobserver.UpdatedItemCalled);
            Assert.IsFalse(testobserver.DeletedItemCalled);
            Assert.IsFalse(testobserver.ResetCalled);
        }

        [TestMethod]
        public void Optimizations_ObservableSource_Refresh()
        {
            items.Publish(dummy1);
            items.Publish(dummy2);

            testobserver.NewItemCalled = false;
            
            testobserver.OnUpdatedItem = item => Assert.AreEqual(dummy1, item);

            items.Refresh(dummy1);

            testobserver.UpdatedItemCalled = false;
            testobserver.OnUpdatedItem = item => Assert.AreEqual(dummy2, item);

            items.Refresh(dummy2);

            Assert.IsFalse(testobserver.NewItemCalled);
            Assert.IsTrue(testobserver.UpdatedItemCalled);
            Assert.IsFalse(testobserver.DeletedItemCalled);
            Assert.IsFalse(testobserver.ResetCalled);
        }

        [TestMethod]
        public void Optimizations_ObservableSource_RefreshAll()
        {
            items.Publish(dummy1);
            items.Publish(dummy2);

            var list = new List<Dummy>();

            testobserver.NewItemCalled = false;
            testobserver.OnUpdatedItem = item => list.Add(item);

            items.RefreshAll();

            list.AssertContainsOnly(dummy1, dummy2);

            Assert.IsFalse(testobserver.NewItemCalled);
            Assert.IsTrue(testobserver.UpdatedItemCalled);
            Assert.IsFalse(testobserver.DeletedItemCalled);
            Assert.IsFalse(testobserver.ResetCalled);
        }

        [TestMethod]
        public void Optimizations_ObservableSource_Reset()
        {
            items.Publish(dummy1);
            items.Publish(dummy2);

            testobserver.NewItemCalled = false;

            items.Reset();

            Assert.IsFalse(testobserver.NewItemCalled);
            Assert.IsFalse(testobserver.UpdatedItemCalled);
            Assert.IsFalse(testobserver.DeletedItemCalled);
            Assert.IsTrue(testobserver.ResetCalled);
        }

        [TestMethod]
        public void Optimizations_ObservableSource_Revoke()
        {
            items.Publish(dummy1);
            items.Publish(dummy2);

            testobserver.NewItemCalled = false;
            
            testobserver.OnDeletedItem = item => Assert.AreEqual(dummy1, item);

            items.Revoke(dummy1);

            testobserver.UpdatedItemCalled = false;
            testobserver.OnDeletedItem = item => Assert.AreEqual(dummy2, item);

            items.Revoke(dummy2);

            Assert.IsFalse(testobserver.NewItemCalled);
            Assert.IsFalse(testobserver.UpdatedItemCalled);
            Assert.IsTrue(testobserver.DeletedItemCalled);
            Assert.IsFalse(testobserver.ResetCalled);
        }
    }
}
